#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include <sys/types.h>
#include "iniconf.h"
#include "log.h"

struct conf_entry;
typedef struct conf_entry {
    char *comment;
    char *key;
    char *value;
    struct conf_entry *next;
} conf_entry_t;

struct conf_group;
typedef struct conf_group {
    char *name;
    char *comment;
    conf_entry_t *entry;
    struct conf_group *next;
} conf_group_t;

struct ini_conf {
    conf_group_t *group;
};

enum {
    ERR_OK = 0,
    ERR_NOFILE = -1,
    ERR_NOMEM = -2,
    ERR_OPEN_FILE = -3,
    ERR_NO_GROUP = -4,
    ERR_NO_KEY = -5,
    ERR_NO_GROUP_NAME = -6,
    ERR_ILLEGAL_VALUE = -7,
    ERR_TRUNCATE = -8,
};

int ini_conf_err(ini_err_t err)
{
    return err != ERR_OK;
}

char *ini_conf_strerror(ini_err_t err)
{
    char *s = NULL;
    switch (err) {
        case ERR_OK:
            s = "ok";
            break;
        case ERR_NOFILE:
            s = "conf file noexist";
            break;
        case ERR_NOMEM:
            s = "no dynamic memory";
            break;
        case ERR_OPEN_FILE:
            s = "failed to open conf file";
            break;
        case ERR_NO_GROUP:
            s = "no group exist";
            break;
        case ERR_NO_KEY:
            s = "no key exist";
            break;
        case ERR_NO_GROUP_NAME:
            s = "no group name";
            break;    
        case ERR_ILLEGAL_VALUE:
            s = "value is illegal";
            break;
        case ERR_TRUNCATE:
            s = "ftruncate() failed";
            break;
        default:
            s = "unknown err";
    }
    return s;
}

struct ini_conf *ini_conf_new(void)
{
    struct ini_conf *conf = NULL;

    conf = calloc(1, sizeof(struct ini_conf));

    return conf;
}

ini_err_t ini_conf_free(struct ini_conf *conf)
{
    conf_group_t *grp, *next_grp = NULL;
    conf_entry_t *entry, *next_entry = NULL;

    if (conf) {
        for (grp=conf->group; grp; grp=next_grp) {
            next_grp = grp->next;
            for (entry=grp->entry; entry; entry=next_entry) {
                next_entry = entry->next;
                if (entry->comment)
                    free(entry->comment);
                free(entry->key);
                free(entry->value);
                free(entry);
            }
            free(grp->name);
            if (grp->comment)
                free(grp->comment);
            free(grp);
        }
        free(conf);
    }

    return ERR_OK;
}
#define is_blank(c) ((c) == ' ' || (c) == '\t')
#define is_group(l) (l[0] == '[' && l[strlen(l)-1] == '\n' && (l[strlen(l)-2] == ']' || (l[strlen(l)-2] == '\r' && l[strlen(l)-3] == ']')))

#if 0 // unused right now
static int is_blank_line(const char *line)
{
    const char *c = NULL;

    for (c=line; c; c++) {
        if (!is_blank(*c)) {
            break;
        }
        if (*c == '\n' || (*c == '\r' && *(c+1) == '\n')) {
            return 1;
        }
    }

    return 0;
}
#endif

static int is_entry(const char *line)
{
    const char *p = NULL, *q = NULL;

    p = strchr(line, '=');
    if (!p) return 0;

    // key只包括" [a-zA-Z0-9]"字符
    for (q=line; q&&q!=p; q++) {
        if (! ((*q == ' ') 
             || (*q >= 'a' && *q <= 'z')
             || (*q >= 'A' && *q <= 'Z')
             || (*q >= '0' && *q <= '9'))) {
            return 0;
        }
    }

    return 1;
}

static char *trim_blank(char *s, char *e)
{
    char *c = NULL;

    for (c = s; c;) {
        if (*c != ' ') break;
        c++;
    }
    if (c != s) s = c;

    for (c = e; c;) {
        if (*c != ' ' && *c != '\r' && *c != '\n') break;
        c--;
    }
    if (c != e) e = c;
    *(e + 1) = '\0';

    return s;
}

static char *get_group_name(char *line)
{
    char *p = NULL, *q = NULL;

    p = strchr(line, '[');
    q = strchr(line, ']');
    if (p && q) {
        return trim_blank(p+1, q-1);        
    }
    return NULL;
}

static void get_entry(char *line, conf_entry_t *entry)
{
    char *p = NULL, *q = NULL;

    p = strchr(line, '=');
    q = &line[strlen(line)-1];
    entry->key = strdup(trim_blank(line, p-1));
    entry->value = strdup(trim_blank(p+1, q));
    printf("%s = %s\n", entry->key, entry->value);
}

/*
 * 全局注释(在第一个group前的)最多一行
 * 在第一个group前的key,value对不处理，即key,value对必须属于某一个group
 */
struct ini_conf *ini_conf_load(const char *file_path, const char *comment_chars, ini_err_t *err)
{
    FILE *fp = NULL;
    char line[256] = {0};
    int start_content = 0;
    struct ini_conf *conf = NULL;
    conf_group_t **groupp = NULL, *grp = NULL;
    conf_entry_t **entryp = NULL, *entry = NULL;
    char *comment = NULL, *cmt = NULL, *group = NULL;
    size_t len;

    if ((fp = fopen(file_path, "rb")) == NULL) {
        *err = ERR_NOFILE;
        return NULL;
    }
    *err = ERR_OK;
    conf = ini_conf_new();
    groupp = &conf->group;
    while (fgets(line, 256, fp)) {
        printf(">>>> %s\n", line);
        if ((len = strspn(line, comment_chars)) >= 1) {
            cmt = trim_blank(&line[1], &line[strlen(line)-1]);
            if (comment) free(comment);
            comment = strdup(cmt);
        }
        else if (is_group(line)) {
            printf("[group]\n");
            if (!start_content) start_content = 1;
            grp = malloc(sizeof(conf_group_t));
            if (!grp) {
                *err = ERR_NOMEM;
                break;
            }
            group = get_group_name(line);
            if (!group) {
                *err = ERR_NO_GROUP_NAME;
                break;
            }
            grp->name = strdup(group);
            if (comment) {
                grp->comment = comment;
                comment = NULL;
            }
            grp->next = NULL;
            *groupp = grp;
            entryp = &grp->entry;
            groupp = &grp->next;
        }
        else if (is_entry(line)) {
            printf("[entry]\n");
            if (!start_content) continue;
            entry = malloc(sizeof(conf_entry_t));
            if (!entry) {
                *err = ERR_NOMEM;
                break;
            }
            if (comment) {
                entry->comment = comment;
                comment = NULL;
            }
            entry->next = NULL;
            get_entry(line, entry);
            if (entryp)
                *entryp = entry;
            entryp = &entry->next;
        }
    }
    fclose(fp);

    if (*err != ERR_OK) {
        ini_conf_free(conf);
        return NULL;
    }
    return conf;
}


ini_err_t ini_conf_save(struct ini_conf *conf, const char *file_path)
{
    ini_err_t err = ERR_OK;
    FILE *fp = NULL;
    int  file_size = 0;
    conf_group_t *grp = NULL;
    conf_entry_t *entry = NULL;

    fp = fopen(file_path, "wb");
    if (!fp) {
        err = ERR_OPEN_FILE;
        return err;
    }

    for (grp=conf->group; grp; grp=grp->next) {
        file_size += fprintf(fp, "\n");
        if (grp->comment)
            file_size += fprintf(fp, "; %s\n", grp->comment);
        file_size += fprintf(fp, "[%s]\n", grp->name);
        for (entry=grp->entry; entry; entry=entry->next) {
            if (entry->comment)
                file_size += fprintf(fp, "; %s\n", entry->comment);
            file_size += fprintf(fp, "%s = %s\n", entry->key, entry->value);
        }
    }
    if (ftruncate(fileno(fp), file_size) != 0) { // 截断文件到当前大小
        err = ERR_TRUNCATE;
    }
    fclose(fp);
    return err;
}

static char *ini_conf_get_value(struct ini_conf *conf, const char *group, const char *key, ini_err_t *err)
{
    conf_group_t *grp = NULL;
    conf_entry_t *entry = NULL;

    for (grp=conf->group; grp; grp=grp->next) {
        if (strcmp(grp->name, group) != 0) {
            *err = ERR_NO_GROUP;
        }
        for (entry=grp->entry; entry; entry=entry->next) {
            if (strcmp(entry->key, key) == 0) {
                *err = ERR_OK;
                return entry->value;
            }
        }
        *err = ERR_NO_KEY;
    }
    return NULL;
}

static conf_entry_t *ini_conf_get_entry(struct ini_conf *conf, const char *group, const char *key, ini_err_t *err)
{
    conf_group_t *grp = NULL, **grpp = &conf->group;
    conf_entry_t *entry = NULL, **entryp = &conf->group->entry;
    
    // 默认为找不到组
    *err = ERR_NO_GROUP;
    for (grp=conf->group; grp; grp=grp->next) {
        if (strcmp(grp->name, group) == 0) {
            for (entry=grp->entry; entry; entry=entry->next) {
                if (strcmp(entry->key, key) == 0) {
                    *err = ERR_OK;
                    return entry; // 找到group + key
                }
                entryp = &entry->next; // 记录下一个entry
            }
            *err = ERR_NO_KEY; // 匹配group，但未找到key
        }
        grpp = &grp->next; // 记录下一个group
    }

    // 新建组
    if (*err == ERR_NO_GROUP) {
        grp = malloc(sizeof(conf_group_t));
        if (!grp) {
            *err = ERR_NOMEM;
            return NULL;
        }
        grp->name = strdup(group);
        grp->next = NULL;
        grp->comment = NULL;
        *grpp = grp;
        entryp = &grp->entry;
    }

    // 新建entry
    entry = calloc(1, sizeof(conf_entry_t));
    if (!entry) {
        if (*err == ERR_NO_GROUP) {
            free(grp->name);
            free(grp);
            *grpp = NULL;
        }
        *err = ERR_NOMEM;
        return NULL;
    }
    entry->key = strdup(key);
    *entryp = entry;
    *err = ERR_OK;
    
    return entry;
}

int ini_conf_get_integer(struct ini_conf *conf, const char *group, const char *key, ini_err_t *err)
{
    char *value = NULL;
    unsigned long val;

    value = ini_conf_get_value(conf, group, key, err);
    if (value) {
        val = strtoul(value, NULL, 0);
        return (int)(val);
    }
    return 0;
}

double ini_conf_get_double(struct ini_conf *conf, const char *group, const char *key, ini_err_t *err)
{
    char *value = NULL;
    double val;

    value = ini_conf_get_value(conf, group, key, err);
    if (value) {
        val = strtod(value, NULL);
        return val;
    }
    return 0.0;
}

ini_bool_t ini_conf_get_bool(struct ini_conf *conf, const char *group, const char *key, ini_err_t *err)
{
    char *value = NULL;

    value = ini_conf_get_value(conf, group, key, err);
    if (value) {
        if (strcasecmp(value, "true") == 0) {
            return INI_TRUE;
        }
        else if (strcasecmp(value, "false") == 0) {
            return INI_FALSE;
        }
        else {
            *err = ERR_ILLEGAL_VALUE;
        }
    }
    return INI_BOOL_UNKNOWN;
}

char *ini_conf_get_string(struct ini_conf *conf, const char *group, const char *key, ini_err_t *err)
{
    char *value = NULL;

    value = ini_conf_get_value(conf, group, key, err);
    return value;
}

ini_err_t ini_conf_set_integer(struct ini_conf *conf, const char *group, const char *key, int value, char *comment)
{
    ini_err_t err = ERR_OK;
    conf_entry_t *entry = NULL;
    char value_str[16] = {0};

    entry = ini_conf_get_entry(conf, group, key, &err);
    if (entry) {
        if (entry->value)
            free(entry->value);
        snprintf(value_str, sizeof(value_str), "%d", value);
        entry->value = strdup(value_str);
        if (comment) {
            if (entry->comment)
                free(entry->comment);
            entry->comment = strdup(comment);
        }
    }

    return err;
}

ini_err_t ini_conf_set_double(struct ini_conf *conf, const char *group, const char *key, double value, char *comment)
{
    ini_err_t err = ERR_OK;
    conf_entry_t *entry = NULL;
    char value_str[16] = {0};

    entry = ini_conf_get_entry(conf, group, key, &err);
    if (entry) {
        if (entry->value)
            free(entry->value);
        snprintf(value_str, sizeof(value_str), "%lf", value);
        entry->value = strdup(value_str);
        if (comment) {
            if (entry->comment)
                free(entry->comment);
            entry->comment = strdup(comment);
        }
    }
    return err;
}

ini_err_t ini_conf_set_bool(struct ini_conf *conf, const char *group, const char *key, ini_bool_t value, char *comment)
{
    ini_err_t err = ERR_OK;
    conf_entry_t *entry = NULL;
    char value_str[8] = {0};

    entry = ini_conf_get_entry(conf, group, key, &err);
    if (entry) {
        if (entry->value)
            free(entry->value);
        snprintf(value_str, sizeof(value_str), "%s", value == INI_TRUE ? "true" : "false");
        entry->value = strdup(value_str);
        if (comment) {
            if (entry->comment)
                free(entry->comment);
            entry->comment = strdup(comment);
        }
    }
    return err;
}

ini_err_t ini_conf_set_string(struct ini_conf *conf, const char *group, const char *key, char *value, char *comment)
{
    ini_err_t err = ERR_OK;
    conf_entry_t *entry = NULL;

    entry = ini_conf_get_entry(conf, group, key, &err);
    if (entry) {
        if (entry->value)
            free(entry->value);
        entry->value = strdup(value);
        if (comment) {
            if (entry->comment)
                free(entry->comment);
            entry->comment = strdup(comment);
        }
    }
    return err;

}

ini_err_t ini_conf_remove_group(struct ini_conf *conf, const char *group)
{
    ini_err_t err = ERR_OK;
    conf_group_t *grp = NULL, *prev = NULL;
    conf_entry_t *entry = NULL, *next = NULL;

    for (grp=conf->group; grp; grp=grp->next) {
        if (strcmp(grp->name, group) == 0) {
            if (prev == NULL) {
                conf->group = grp->next;
            }
            else {
                prev->next = grp->next;
            }
            for (entry = grp->entry; entry; entry = next) {
                next = entry->next;
                free(entry->key);
                free(entry->value);
                if (entry->comment)
                    free(entry->comment);
                free(entry);
            }
            free(grp);
            return err;
        }

        prev = grp;
    }
    err = ERR_NO_GROUP;
    return err;
}

ini_err_t ini_conf_remove_key(struct ini_conf *conf, const char *group, const char *key)
{
    ini_err_t err = ERR_OK;
    conf_group_t *grp = NULL;
    conf_entry_t *entry, *prev = NULL;

    for (grp=conf->group; grp; grp=grp->next) {
        if (strcmp(grp->name, group) != 0) {
            err = ERR_NO_GROUP;
            continue;
        }
        for (entry=grp->entry; entry; entry=entry->next) {
            if (strcmp(entry->key, key) == 0) {
                if (prev == NULL) {
                    grp->entry = entry->next;
                }
                else {
                    prev->next = entry->next;
                }
                free(entry->key);
                free(entry->value);
                if (entry->comment)
                    free(entry->comment);
                free(entry);
                err = ERR_OK;
                return err;
            }
            prev = entry;
        }
        err = ERR_NO_KEY;
    }

    return err;
}


void ini_conf_dump(struct ini_conf *conf)
{
    conf_group_t *grp = NULL;
    conf_entry_t *entry = NULL;

    printf("\n----------------------------------\n");
    for (grp=conf->group; grp; grp=grp->next) {
        if (grp->comment)
            printf("; %s\n", grp->comment);
        printf("[%s]\n", grp->name);
        for (entry=grp->entry; entry; entry=entry->next) {
            if (entry->comment)
                printf("; %s\n", entry->comment);
            printf("%s = %s\n", entry->key, entry->value);
        }
        printf("\n");
    }
    printf("----------------------------------\n");
}

